package com.fa4j.common.web.config;

import com.fa4j.common.base.model.ModuleInfo;
import com.fa4j.common.base.util.ModuleUtil;
import com.fa4j.common.web.annotation.ApiMethod;
import com.fa4j.common.web.aspect.CommonWebAspect;
import com.fa4j.common.web.controller.ErrorController;
import com.fa4j.common.web.controller.ErrorControllerAdvice;
import com.fa4j.common.web.controller.JsonResponseBodyAdvice;
import com.fa4j.common.web.convert.StringToLocalDateTimeConverter;
import com.fa4j.common.web.interceptor.CommonWebHandlerInterceptor;
import com.fa4j.common.web.model.AppApi;
import com.fa4j.common.web.resolver.ClientInfoArgumentResolver;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.annotation.Order;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.pattern.PathPattern;

import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@AutoConfiguration
@Import({
        JsonResponseBodyAdvice.class,
        ErrorControllerAdvice.class,
        OpenApiConfiguration.class
})
@AutoConfigureBefore(ErrorMvcAutoConfiguration.class)
@EnableConfigurationProperties(CommonWebPropsConfig.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@PropertySource(value = "classpath:common-web-default.properties")
public class CommonWebAutoConfiguration {

    @Bean
    public CommonWebAspect logAspect(CommonWebPropsConfig config, ApplicationContext context) {
        return new CommonWebAspect(config, context);
    }

    @Bean
    @Order(Integer.MIN_VALUE)
    @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
    public ErrorController basicErrorController(ErrorAttributes errorAttributes,
                                                ObjectProvider<ErrorViewResolver> errorViewResolvers) {
        return new ErrorController(errorAttributes, errorViewResolvers.orderedStream().collect(Collectors.toList()));
    }

    @Bean
    @Order(-1)
    public WebMvcConfigurer mvcAutoConfigurer(CommonWebPropsConfig config) {
        return new WebMvcConfigurer() {
            @Override
            public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
                resolvers.add(new ClientInfoArgumentResolver());
            }

            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(new CommonWebHandlerInterceptor(config))
                        .order(Integer.MIN_VALUE)
                        .addPathPatterns("/**")
                        .excludePathPatterns("/swagger-ui.html", "/webjars/**", "/static/**", "/error", "/v3/**");
            }

            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedOriginPatterns("*")
                        .allowedHeaders("*")
                        .allowedMethods("*")
                        .allowCredentials(true)
                        .maxAge(3600);
            }

            @Override
            public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
                WebMvcConfigurer.super.configureMessageConverters(converters);
            }

            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new StringToLocalDateTimeConverter("yyyy-MM-dd HH:mm:ss"));
            }
        };
    }

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return builder -> {
            // formatter
            DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

            // deserializers
            builder.deserializers(new LocalDateDeserializer(dateFormatter));
            builder.deserializers(new LocalDateTimeDeserializer(dateTimeFormatter));

            // serializers
            builder.serializers(new LocalDateSerializer(dateFormatter));
            builder.serializers(new LocalDateTimeSerializer(dateTimeFormatter));
        };
    }

    @Bean
    public List<AppApi> apiList(RequestMappingHandlerMapping requestMappingHandlerMapping) {
        List<AppApi> result = new ArrayList<>();
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
        handlerMethods.forEach((mappingInfo, handlerMethod) -> {
            ApiMethod apiMethod = handlerMethod.getMethodAnnotation(ApiMethod.class);
            if (apiMethod != null && mappingInfo.getPathPatternsCondition() != null) {
                for (RequestMethod method : mappingInfo.getMethodsCondition().getMethods()) {
                    for (PathPattern path : mappingInfo.getPathPatternsCondition().getPatterns()) {
                        ModuleInfo module = ModuleUtil.getModule(handlerMethod.getBeanType());
                        AppApi api = new AppApi();
                        api.setModuleName(module.getName());
                        api.setModuleCode(module.getCode());
                        api.setName(apiMethod.name());
                        api.setAppTypes(apiMethod.type());
                        api.setHttpPath(path.getPatternString());
                        api.setHttpMethod(method.name());
                        result.add(api);
                    }
                }
            }
        });
        return result;
    }


}
